package com.pactera.asmp.server.common.websocket;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pactera.asmp.server.common.lock.Lock;
import com.pactera.asmp.server.common.utils.SpringUtil;
import com.pactera.asmp.server.job.dispatch.impl.AsmpTaskDispatchBus;
import com.pactera.asmp.server.job.dispatch.impl.feedback.FeedbackManager;
import com.pactera.asmp.server.pojo.SocketMessage;
import com.pactera.asmp.server.pojo.WSMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@ServerEndpoint("/ws/device/v1/{id}")
@Component
public class WebSocketServerDevice {
    private Logger logger = LoggerFactory.getLogger(WebSocketServerDevice.class);
    private static final ObjectMapper objectMapper = new ObjectMapper();

    private Lock lock;
    private SocketUtil socketUtil;
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private FeedbackManager feedbackManager;

    public static volatile ConcurrentHashMap<String, Session> id2sessionMap = new ConcurrentHashMap<>();
    public static volatile ConcurrentHashMap<Session, String> session2idMap = new ConcurrentHashMap<>();

    @OnOpen
    public void onOpen(Session session, @PathParam("id") String id) throws IOException {
        // 一个设备只支持一个socket连接！！！
        if (StringUtils.isEmpty(id) || (id.length() != 36 && id.length() != 25)) {
            session.close();
            return;
        }
        lockService().lock(id);
        try {
            Long size = stringRedisTemplate().opsForSet().size(SocketUtil.REDIS_KEY_WS_DEVICE_INSTANCE + ":" + id);
            if(size != null && size >0){
                logger.info("Device 设备已在线：{}",id);
                session.close();
                return;
            }

            String sessionKey = socketUtil().formatSessionId(id,session);
            if(StringUtils.isEmpty(sessionKey)){
                session.close();
                return;
            }
            logger.info("Device onOpen =================================================================================================================={}", sessionKey);
            session.setMaxIdleTimeout(15000);  // 15秒超时过期
            // 1. 本地缓存
            id2sessionMap.put(sessionKey, session);
            session2idMap.put(session, sessionKey);
            // 2. 分布式Session服务注册
            socketUtil().registerSocketServer(sessionKey);
            // 3. 心跳信息
            socketUtil().heartBeatById(sessionKey);
            // 4. 成功连接反馈
            socketUtil().openSuccessMessage(session, id,sessionKey);
            //通知，可获取duid
            AsmpTaskDispatchBus.getAsmpDispatchBus().deviceOnlineEvent(id);
        }
        finally {
            lockService().unlock(id);
        }
    }

    @OnClose
    public void onClose(Session session) {
        if (session2idMap.containsKey(session)) {
            String id = session2idMap.get(session);
            String deviceid = id.split("@")[0];
            lockService().lock(deviceid);
            try {
                logger.info("Device onClose =================================================================================================================={}", id);
                id2sessionMap.remove(id);
                session2idMap.remove(session);
                // 更新设备状态
                socketUtil().updateDeviceStatus(id.split("@")[0]);
                socketUtil().offlineRedisCache(id);

                // 解绑关联
                String userid = stringRedisTemplate().opsForValue().get(deviceid);
                if(!StringUtils.isEmpty(userid)) {
                    stringRedisTemplate().delete(deviceid);  // 真机一对一关联
                    stringRedisTemplate().delete(userid);  // 真机一对一关联
                }
                stringRedisTemplate().opsForHash().delete(SocketUtil.REDIS_KEY_SOCKET_SERVER_DEVICE,id);  //  SERVER 关联
                stringRedisTemplate().opsForSet().remove(SocketUtil.REDIS_KEY_WS_DEVICE_INSTANCE+":"+deviceid,id); // 设备ID和实例ID的关联

                WSMessage message = new WSMessage();
                message.setMsgcode("201");
                message.setDescribe("连接已断开");
                byte[] bytes = objectMapper.writeValueAsBytes(message);
                String routingKey;

                // 设备绑定的真机用户
                if(!StringUtils.isEmpty(userid)) {
                    socketUtil().sendMQ(id,userid,bytes,"json",null);
                    String canUserId = "CAN"+userid;
                    Set<String> members = stringRedisTemplate().opsForSet().members(SocketUtil.REDIS_KEY_WS_USER_INSTANCE + ":" + canUserId);
                    if(!CollectionUtils.isEmpty(members)){
                        socketUtil().sendMQ(id,canUserId,bytes,"json",null);
                    }
                }
                // 设备绑定的大屏用户

                Set<String> members = stringRedisTemplate().opsForSet().members(SocketUtil.REDIS_KEY_SET_SCREEN_DEVICE_SESSION + ":" + deviceid);
                if(!CollectionUtils.isEmpty(members)){
                    for(String member : members){ // eg:  member->sceen:13@abcd
                        routingKey = (String) stringRedisTemplate().opsForHash().get(SocketUtil.REDIS_KEY_SOCKET_SERVER_SCREEN,member);
                        socketUtil().sendMQ(routingKey,new SocketMessage(id,member.split("@")[0],"json",bytes,null));
                    }
                }
            } catch (JsonProcessingException e) {
                logger.info(e.getMessage());
            } finally {
                lockService().unlock(deviceid);
            }
        }
    }


    @OnMessage
    public void onMessage(String message, Session session) {
        // 1. ping#   --> 心跳消息
        // 2. 运行状态信息
        // 3. Can信息
        // 4. 其他信息

        if(StringUtils.isEmpty(message)) return;

        if(message.startsWith("ping#")){
            String id = session2idMap.get(session);
            message = "ping#"+id;
            socketUtil().heartBeatByMsg(message);
            return;
        }
        String id = session2idMap.get(session);
        String deviceid = id.split("@")[0];

        // 上位机运行状态信息
        if(message.startsWith("computerstate:")){
            stateMessage(session,"computerstate:",WebSocketServerScreen.SCREEN_CODE_CONNECTED_COMPUTERS_RUNTIME,WebSocketServerScreen.SCREEN_CODE_DISCONNECTED_COMPUTER_RUNTIME,SocketUtil.REDIS_KEY_SET_SCREEN_COMPUTER_SESSION,message, id, deviceid);
            return;
        }
        // 设备运行状态信息
        if(message.startsWith("duidstate:")){
            stateMessage(session,"duidstate:",WebSocketServerScreen.SCREEN_CODE_CONNECTED_DUID_RUNTIME,WebSocketServerScreen.SCREEN_CODE_DISCONNECTED_DUID_RUNTIME,SocketUtil.REDIS_KEY_SET_SCREEN_DUID_SESSION,message, id, deviceid);
            return;
        }
        // CAN相关信息
        if(message.startsWith("caninfo:")){
            caninfoTransfer(session,message.substring(8),deviceid);
            return;
        }
        // 设备包名
        if(message.startsWith("packagename:")){
            stringRedisTemplate.opsForValue().set(SocketUtil.REDIS_KEY_WS_DEVICE_PACKAGE_NAME + ":" + deviceid, message.substring(12), 1, TimeUnit.MINUTES);
            return;
        }

        // 其他信息：脚本执行信息等
        try {
            this.message(message.getBytes("utf-8"), session,"json");
        } catch (UnsupportedEncodingException e) {
            logger.info(e.getMessage());
        }
    }

    private void caninfoTransfer(Session session,String message,String deviceId){
        try {
            String userid = stringRedisTemplate().opsForValue().get(deviceId);
            WSMessage msg = new WSMessage();
            msg.setFromuuid(deviceId);
            msg.setTouuid(userid);
            msg.setMsgcode(WebSocketServerWebCan.CODE_DATA_CAN_START);
            msg.setDescribe("CAN信息数据");
            msg.setList(Arrays.asList(message));
            this.message(objectMapper.writeValueAsBytes(msg),session,"json");
        }
        catch (JsonProcessingException e) {
            logger.info(e.getMessage());
        }
    }

    private void stateMessage(Session session, String tag,String msgCode,String disMsgCode,String redisKeyPrefix,String message, String id, String formId) {
        Set<String> members = stringRedisTemplate().opsForSet().members( redisKeyPrefix+ ":" + formId);

        if(CollectionUtils.isEmpty(members)){
            // 找不到接收方，则通知不需要发送运行状态了
            logger.info("停止发送运行时状态信息-------------------tag:{}-------msgCode:{}--------fromId:{}--------------redisKeyPrefix:{}",tag,disMsgCode,formId,redisKeyPrefix);
            disConnetMessage(session,disMsgCode,"停止发送运行时状态信息");
            return;
        }

        message = message.substring(tag.length());
        WSMessage wsMessage = new WSMessage();
        wsMessage.setFromuuid(formId);
        wsMessage.setSocketKey(id);
        wsMessage.setMsgcode(msgCode);
        wsMessage.setList(Arrays.asList(message));
        for(String member : members){
            wsMessage.setTouuid(member.split("@")[0]);
            try {
                socketUtil().sendMQ(formId,wsMessage.getTouuid(),objectMapper.writeValueAsBytes(wsMessage),"json",null);
            } catch (JsonProcessingException e) {
                logger.info(e.getMessage());
            }
        }
    }

    private void disConnetMessage(Session session,String code,String desc) {
        if (session.isOpen()) {
            WSMessage msg = new WSMessage();
            msg.setMsgcode(code);
            msg.setDescribe(desc);
            try {
                session.getBasicRemote().sendText(objectMapper.writeValueAsString(msg));
            } catch (IOException e) {
                logger.info(e.getMessage());
            }
        }
    }

    private boolean message(byte[] message, Session session,String type) {
        boolean findFlag =false;
        String id = session2idMap.get(session);
        String deviceid = id.split("@")[0];
        String userid = stringRedisTemplate().opsForValue().get(deviceid);
        if(!StringUtils.isEmpty(userid)){
            String routingKey;
            Set<String> members = stringRedisTemplate().opsForSet().members(SocketUtil.REDIS_KEY_WS_USER_INSTANCE +":"+ userid);
            Set<String> members1 = stringRedisTemplate().opsForSet().members(SocketUtil.REDIS_KEY_WS_USER_INSTANCE + ":CAN" + userid);
            members.addAll(members1);
            if(WebSocketServerUserAux.auxMap.get(deviceid)!=null){
                Set<String> members2 = stringRedisTemplate().opsForSet().members(SocketUtil.REDIS_KEY_WS_USER_INSTANCE + ":aux" + userid.replace("aux", ""));
                members.clear();
                members.addAll(members2);
            }
            if(!CollectionUtils.isEmpty(members)){
                for(String member : members){ // eg:  member-> 46@abcd
                    routingKey = (String) stringRedisTemplate().opsForHash().get(SocketUtil.REDIS_KEY_SOCKET_SERVER_USER,member);
                    socketUtil().sendMQ(routingKey,new SocketMessage(id,member.split("@")[0],type,message,null));
                    findFlag = true;
                }
            }
        }
        return findFlag;
    }

    @OnMessage
    public void onMessage(byte[] bytes, Session session) {
        String id = session2idMap.get(session);
        String deviceid = id.split("@")[0];
        boolean imgFlag = this.message(bytes, session, "img");

        // 转发大屏
        boolean screenFlag = false;
        String routingKey;
        Set<String> members = stringRedisTemplate().opsForSet().members(SocketUtil.REDIS_KEY_SET_SCREEN_DEVICE_SESSION + ":" + deviceid);
        if(!CollectionUtils.isEmpty(members)){
            for(String member : members){ // eg:  member-> sceen:13@abcd
                routingKey = (String) stringRedisTemplate().opsForHash().get(SocketUtil.REDIS_KEY_SOCKET_SERVER_SCREEN,member);
                socketUtil().sendMQ(routingKey,new SocketMessage(id,member.split("@")[0],"img",bytes,null));
                screenFlag = true;
            }
        }
        if(!screenFlag && !imgFlag){
            // 找不到接收方，则通知不需要发送图片了
            logger.info("DeviceWS onMessage byte[] 停止发送图片(大屏)-----------{}",id);
            disConnetMessage(session,WebSocketServerScreen.SCREEN_CODE_DISCONNECTED_IMAGE,"停止发送图片");

            // 找不到接收方，则通知不需要发送图片了
            logger.info("DeviceWS onMessage byte[] 停止发送图片(真机)-----------{}",id);
            disConnetMessage(session,"201","停止发送图片");
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        String id = session2idMap.get(session);
        logger.error("设备连接错误:{},原因:{}", id, error.getMessage());
    }

    private Lock lockService(){
        if(this.lock == null) this.lock =  SpringUtil.getBean(Lock.class);
        return this.lock;
    }
    private SocketUtil socketUtil(){
        if(this.socketUtil == null) this.socketUtil =  SpringUtil.getBean(SocketUtil.class);
        return this.socketUtil;
    }
    private StringRedisTemplate stringRedisTemplate(){
        if(this.stringRedisTemplate == null) this.stringRedisTemplate =  SpringUtil.getBean(StringRedisTemplate.class);
        return this.stringRedisTemplate;
    }
}

